A bug in cvs versions up to and including 1.11.4 was recently found where, under certain conditions, a pointer is free()'d, and then free()'d again without being re-initialised. The reports with regards to the exploitability of the condition in question range from - "it is a classical exploitable double-free()" to "may possibly be exploited". I have written an exploit for Linux for pserver, and contrary to my usual practice, decided to make it public. First, I couldn't find any papers on the internet that would explain the exploitation techniques of double-free(), and I believe we don't have many publically available exploits or in-depth discussion on the matter. I hope that this little explanation that I've put together, and the exploit itself may be somewhat useful to the hacker/security community (we can't exist without each other, can we? :) The impact of a successful exploitation is not that great: an unprivileged access to the system, where your calls to getuid() will return a number that's far from 0 (cvs drops provileges, and does it right). The audience is expected to be familiar with D.L. malloc implementation. The explanation of how D.L. malloc works can be found in two articles in phrack 57. If a request for a memory chunk is made, and if chunks that are kept in linked lists, or the last remaindered chunk cannot satisfy the requirement, the top memory chunk is split off, and a chunk of the right size is returned to malloc(). When this chunk is later free()'d, it may be coalesced with other adjacent chunks if any of the adjacent chunks are free. If not, the chunk is placed in a linked list. After being processed by the frontlink() macro, the linked list looks like this: we have two items in the list, the bin and the chunk, both BK and FD pointers of the bin point to the chunk, and both BK and FD pointers of the chunk point to the bin. Now, should this chunk be free()'d again, while on the linked list, the picture changes. After the second free() is called and the chunk is processed by the frontlink() again, we have both BK and FD pointers of the bin still pointed at the chunk, but both BK and FD pointers of the chunk will point to itself !!! Take a look now at the unlink() macro. This macro is called when taking a chunk off the list: #define unlink( P, BK, FD ) { \ BK = P->bk; \ FD = P->fd; \ FD->bk = BK; \ BK->fd = FD; \ } Remember that we have now P = P->bk = P->fd. What changes when this chunk is passed though unlink()? Nothing! This means that ALL subsequent calls to malloc of the size our chunk will be returning the same chunk, the one that was double-free()'d. The rest is easy. After the chunk was double-free()'d, we make a request to the program that will have to allocate the double-free()'d chunk back to us, and copy the data we supply into the memory returned to us. Well, since the chunk is allocated, the backward and forward pointers are not used, and user data gets straight there. We will copy 2 addresses into the first 8 bytes of the chunk. Now, we make another request to the program that will have to allocate to us the same chunk. It will be passed through the unlink() again, but this time, since the chunk is considered free, its BK and FD pointers are used, and lo and behold! We can overwrite any address in the memory with 4 bytes of our choosing. Now, how this particular exploit works: 1. First we allocate a chunk of some size and make sure this chunk comes from the top memory chunk. Also make sure that this chunk stays allocated while we're exploiting. This will keep our directory chunk from being coalesced with the previous chunk. 2. Allocate the Directory chunk, make sure it comes from the top memory chunk. 3. Allocate a chunk the same size as in step 1, for the same reason, except that it will keep our Directory chunk from being coalesced with the next chunk. 4. Now that our exploitable chunk is secure, allocate a big chunk for us to put shellcode, jumps and noops, 4K in this exploit. 5. free() our directory chunk twice. 6. Ask the server to malloc() a chunk of the size that was double-free()'d, it will give us the very same double-free()'d chunk without actually taking it off its linked list; 7. the server will strcpy() our 2 addresses we provide into the first 8 bytes of our double-free()'d once malloc()'ed chunk. 8. Ask the server to again malloc() a chunk of the size that was double-free()'d, upon which again our chunk is malloc()'ed, passed through unlink(), overwriting memory.